package org.dromara.llm.llm;

import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.llm.domain.*;
import org.dromara.llm.domain.vo.SysMessageVo;
import org.dromara.llm.domain.vo.SysTemplateVo;
import org.dromara.llm.llm.api.StreamResponseListener;
import org.dromara.llm.llm.factory.ChatModelFactory;
import org.dromara.llm.llm.memory.DatabaseChatMemory;
import org.dromara.llm.service.ISysLlmConfigService;
import org.dromara.llm.service.ISysTemplateService;
import org.dromara.llm.util.EmojiUtils;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
import org.springframework.ai.chat.messages.*;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * 负责管理和协调LLM相关功能
 */
@Service
@Slf4j
public class ChatService {
    public static final String TOOL_CONTEXT_SESSION_KEY = "session";
    // 句子结束标点符号模式（中英文句号、感叹号、问号）
    private static final Pattern SENTENCE_END_PATTERN = Pattern.compile("[。！？!?]");
    // 逗号、分号等停顿标点
    private static final Pattern PAUSE_PATTERN = Pattern.compile("[，、；,;]");
    // 冒号和引号等特殊标点
    private static final Pattern SPECIAL_PATTERN = Pattern.compile("[：:\"]");
    // 换行符
    private static final Pattern NEWLINE_PATTERN = Pattern.compile("[\n\r]");
    // 数字模式（用于检测小数点是否在数字中）
    private static final Pattern NUMBER_PATTERN = Pattern.compile("\\d+\\.\\d+");
    // 表情符号模式
    private static final Pattern EMOJI_PATTERN = Pattern.compile("\\p{So}|\\p{Sk}|\\p{Sm}");
    // 最小句子长度（字符数）
    private static final int MIN_SENTENCE_LENGTH = 5;
    // 新句子判断的字符阈值
    private static final int NEW_SENTENCE_TOKEN_THRESHOLD = 8;
    // 历史记录默认限制数量
    private static final int DEFAULT_HISTORY_LIMIT = 10;
    /**
     * spring ai 聊天记忆
     */
    protected ChatMemory chatMemory;
    @Resource
    private DatabaseChatMemory chatMemoryRepository; // 数据库聊天记忆
    @Resource
    private ChatModelFactory chatModelFactory;
    @Resource
    private ISysLlmConfigService sysLlmConfigService;
    @Resource
    private ISysTemplateService sysTemplateService;

    /**
     * 处理用户查询（同步方式）
     *
     * @param message 用户消息
     * @return 模型回复
     */
    public String chatText(String message) {
        Prompt prompt = buildPrompt(message, null);
        return chat(prompt);
    }

    /**
     * 依据模板处理用户查询（同步方式）
     *
     * @param message       用户消息
     * @param sysTemplateVo 系统模板
     * @return 模型回复
     */
    public String chatBySystemTemplate(String message, SysTemplateVo sysTemplateVo) {
        Prompt prompt = buildPrompt(message, sysTemplateVo.getTemplateContent());
        return chat(prompt);
    }

    /**
     * 大模型对话
     * 采用缺省配置
     * @param prompt 模型输入
     * @return 模型回复
     */
    public String chat(Prompt prompt) {
        return chat(prompt, sysLlmConfigService.queryDefault());
    }

    /**
     * 大模型对话, 可以输入大模型配置参数
     * @param prompt 模型输入
     * @return 模型回复
     */
    public String chat(Prompt prompt,SysLlmConfig config) {
        try {
            ChatModel chatModel = chatModelFactory.createChatModel(config);
            ChatResponse chatResponse = chatModel.call(prompt);
            if (chatResponse == null || chatResponse.getResult().getOutput().getText() == null) {
                log.warn("模型响应为空或无生成内容");
                return "抱歉，我在处理您的请求时遇到了问题。请稍后再试。";
            }
            String response = chatResponse.getResult().getOutput().getText();
            log.info("大模型回复: {}", response);
            return response;

        } catch (Exception e) {
            log.error("处理查询时出错: {}", e.getMessage(), e);
            return "抱歉，我在处理您的请求时遇到了问题。请稍后再试。";
        }
    }

    /**
     * 提示词构造
     * TODO 后续考虑模板提示词功能 PromptTemplate
     * @param userMessageStr   用户输入
     * @param systemMessageStr 系统提示
     * @return 提示词
     */
    private Prompt buildPrompt(String userMessageStr, String systemMessageStr) {
        List<Message> messages = new ArrayList<>();
        if (StringUtils.isNotBlank(systemMessageStr)) {
            SystemMessage systemMessage = new SystemMessage(systemMessageStr);
            messages.add(systemMessage);
        }
        if (StringUtils.isNotBlank(userMessageStr)) {
            UserMessage userMessage = new UserMessage(userMessageStr);
            messages.add(userMessage);
        }
        return new Prompt(messages);
    }

    /**
     * 处理用户查询（流式方式）
     *
     * @param message 用户消息
     */
    public Flux<ChatResponse> chatStream(String message) {
        SysLlmConfig config = sysLlmConfigService.queryDefault();
        // 获取ChatModel
        ChatModel chatModel = chatModelFactory.createChatModel(config);
        UserMessage userMessage = new UserMessage(message);
        Prompt prompt = new Prompt(userMessage);

        return chatModel.stream(prompt);
    }

    /**
     * 处理用户查询（流式方式）
     * 带记忆功能
     *
     * @param message 用户消息
     */
    public Flux<ChatResponse> chatStreamMemory(String message) {
        SysLlmConfig config = sysLlmConfigService.queryDefault();
        // 获取ChatModel
        ChatModel chatModel = chatModelFactory.createChatModel(config);

        if (chatMemory == null) {//如果记忆没初始化，则初始化一下
            initializeHistory();
        }
        LoginUser loginUser = LoginHelper.getLoginUser();
        // 获取格式化的历史记录（包含当前用户消息）
        List<Message> historyMessages = null;
        if (loginUser != null) {
            historyMessages = chatMemory.get(loginUser.getUserId().toString());
        }

        UserMessage userMessage = new UserMessage(message);
        if (historyMessages != null) {
            historyMessages.add(userMessage);
        } else {
            historyMessages = new ArrayList<>();
            historyMessages.add(userMessage);
        }

        Prompt prompt = new Prompt(historyMessages);

        return chatModel.stream(prompt);
    }

    /**
     * 处理用户查询（流式方式）
     * 带记忆功能，每句处理
     *
     * @param message 用户消息
     */
    public void chatStreamBySentence(String message, TriConsumer<String, Boolean, Boolean> sentenceHandler) {
        try {
            // 创建流式响应监听器
            StreamResponseListener streamListener = new TokenStreamResponseListener(message, sentenceHandler);
            final StringBuilder toolName = new StringBuilder(); // 当前句子的缓冲区
            // 调用现有的流式方法
            chatStream(message)
                .subscribe(
                    chatResponse -> {
                        String token = chatResponse.getResult() == null || chatResponse.getResult().getOutput() == null
                            || chatResponse.getResult().getOutput().getText() == null ? "" : chatResponse.getResult().getOutput().getText();
                        if (!token.isEmpty()) {
                            try {
                                streamListener.onToken(token);
                            } catch (IOException e) {
                                throw new RuntimeException(e);
                            }
                        }
                    },
                    streamListener::onError,
                    () -> {
                        try {
                            streamListener.onComplete();
                        } catch (IOException e) {
                            throw new RuntimeException(e);
                        }
                    }
                );
        } catch (Exception e) {
            log.error("处理LLM时出错: {}", e.getMessage(), e);
            // 发送错误信号
            try {
                sentenceHandler.accept("抱歉，我在处理您的请求时遇到了问题。", true, true);
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
        }
    }

    /**
     * 保存单句，包含大模型回复消息两条数据
     *
     * @param messageVo 消息内容
     * @param response  响应内容
     */
    public int saveSentence(SysMessageVo messageVo, String response, String ttsFlag) {
        log.info("保存单句消息SysMessageVo: {},响应消息response:{},ttsFlag:{}", messageVo, response, ttsFlag);
        // 保存用户消息，会被持久化至数据库。
        messageVo.setSender("user");
        int i = chatMemoryRepository.addMessage(messageVo, TtsFlagCode.F.getCode());

        if (response != null && !response.isEmpty()) {
            // 保存AI消息，会被持久化至数据库。
            messageVo.setMessage(response);
            messageVo.setSender("assistant");
            i = i + chatMemoryRepository.addMessage(messageVo, ttsFlag);
        }
        return i;
    }

    /**
     * 添加消息，包含大模型回复消息一条数据
     *
     * @param messageVo   添加的消息内容
     * @param ttsFlagCode 语音标志
     */
    public int addMessage(SysMessageVo messageVo, TtsFlagCode ttsFlagCode) {
        return chatMemoryRepository.addMessage(messageVo, ttsFlagCode.getCode());
    }

    /**
     * 初始化设备的历史记录缓存
     */
    public void initializeHistory() {
        // 如果缓存中不存在该设备的历史记录，则初始化缓存。默认情况下，只缓存当前会话的聊天记录。
        // 同一个设备重新连接至服务器，会被标识为不同的sessionId。
        // 可以将这理解为spring-ai的conversation会话,将sessionId作为conversationId
        // 从数据库加载历史记录
        LoginUser loginUser = LoginHelper.getLoginUser();

        List<SysMessage> history = chatMemoryRepository.getMessages(loginUser.getUserId().toString(), "NORMAL", DEFAULT_HISTORY_LIMIT);

        MessageWindowChatMemory chatMsgMemory = MessageWindowChatMemory.builder()
            .maxMessages(DEFAULT_HISTORY_LIMIT)
            .build();// 创建一个新的MessageWindowChatMemory实例，限制为10条消息滚动

        SysTemplate sysTemplate = sysTemplateService.queryDefault();
        SystemMessage systemMessage = new SystemMessage(sysTemplate.getTemplateContent());
        chatMsgMemory.add(loginUser.getUserId().toString(), systemMessage);
        chatMsgMemory.add(loginUser.getUserId().toString(), convert(history));

        this.chatMemory = chatMsgMemory;
        log.info("已初始化用户 {} 的历史记录缓存，共 {} 条消息", loginUser.getUserId().toString(), history.size());
    }

    /**
     * 添加用户消息
     *
     * @param message 用户消息
     */
    public void addUserMessage(String message, String messageType, LoginUser loginUser) {
        // 更新缓存
        chatMemoryRepository.addMessage(MessageType.USER.getValue(), message, messageType, null, loginUser);
    }

    /**
     * 添加AI消息
     *
     * @param message AI消息
     */
    public void addAssistantMessage(String message, String messageType, LoginUser loginUser) {
        chatMemoryRepository.addMessage(MessageType.ASSISTANT.getValue(), message, messageType, null, loginUser);
    }

    /**
     * 通用添加消息
     *
     * @param message     消息内容
     * @param role        角色名称
     * @param messageType 消息类型
     */
    public void addMessage(String message, String role, String messageType, String audioPath, LoginUser loginUser) {
        chatMemoryRepository.addMessage(role, message, messageType, audioPath, loginUser);
    }


    /**
     * 将数据库记录的SysMessag转换为spring-ai的Message。
     * 加载的历史都是普通消息(SysMessage.MESSAGE_TYPE_NORMAL)
     *
     * @param messages
     * @return
     */
    private List<Message> convert(List<SysMessage> messages) {
        if (messages == null || messages.isEmpty()) {
            return Collections.emptyList();
        }
        return messages.stream()
            .filter(message -> MessageType.ASSISTANT.getValue().equals(message.getSender())
                || MessageType.USER.getValue().equals(message.getSender()))
            .map(message -> {
                String role = message.getSender();
                // 一般消息("messageType", "NORMAL");//默认为普通消息
                Map<String, Object> metadata = Map.of("messageId", message.getId(), "messageType", message.getMessageType());
                return switch (role) {
                    case "assistant" -> new AssistantMessage(message.getMessage(), metadata);
                    case "user" -> new UserMessage(message.getMessage());
                    default -> throw new IllegalArgumentException("Invalid role: " + role);
                };
            }).collect(Collectors.toList());
    }

    /**
     * 判断文本是否包含实质性内容（不仅仅是空白字符或标点符号）
     *
     * @param text 要检查的文本
     * @return 是否包含实质性内容
     */
    private boolean containsSubstantialContent(String text) {
        if (text == null || text.trim().length() < MIN_SENTENCE_LENGTH) {
            return false;
        }

        // 移除所有标点符号和空白字符后，检查是否还有内容
        String stripped = text.replaceAll("[\\p{P}\\s]", "");
        return stripped.length() >= 2; // 至少有两个非标点非空白字符
    }

    /**
     * 三参数消费者接口
     */
    public interface TriConsumer<T, U, V> {
        void accept(T t, U u, V v) throws IOException;
    }

    class TokenStreamResponseListener implements StreamResponseListener {

        final StringBuilder currentSentence = new StringBuilder(); // 当前句子的缓冲区
        final StringBuilder contextBuffer = new StringBuilder(); // 上下文缓冲区，用于检测数字中的小数点
        final AtomicInteger sentenceCount = new AtomicInteger(0); // 已发送句子的计数
        final StringBuilder fullResponse = new StringBuilder(); // 完整响应的缓冲区
        final AtomicBoolean finalSentenceSent = new AtomicBoolean(false); // 跟踪最后一个句子是否已发送
        String message;//用户消息内容
        TriConsumer<String, Boolean, Boolean> sentenceHandler;

        public TokenStreamResponseListener(String message, TriConsumer<String, Boolean, Boolean> sentenceHandler) {
            this.message = message;
            this.sentenceHandler = sentenceHandler;
        }

        @Override
        public void onToken(String token) throws IOException {
            if (token == null || token.isEmpty()) {
                return;
            }
            // 将token添加到完整响应
            fullResponse.append(token);

            // 逐字符处理token
            for (int i = 0; i < token.length(); ) {
                int codePoint = token.codePointAt(i);
                String charStr = new String(Character.toChars(codePoint));

                // 将字符添加到上下文缓冲区（保留最近的字符以检测数字模式）
                contextBuffer.append(charStr);
                if (contextBuffer.length() > 20) { // 保留足够的上下文
                    contextBuffer.delete(0, contextBuffer.length() - 20);
                }

                // 将字符添加到当前句子缓冲区
                currentSentence.append(charStr);

                // 检查各种断句标记
                boolean shouldSendSentence = false;
                boolean isEndMark = SENTENCE_END_PATTERN.matcher(charStr).find();
                boolean isPauseMark = PAUSE_PATTERN.matcher(charStr).find();
                boolean isSpecialMark = SPECIAL_PATTERN.matcher(charStr).find();
                boolean isNewline = NEWLINE_PATTERN.matcher(charStr).find();
                boolean isEmoji = EmojiUtils.isEmoji(codePoint);

                // 检查当前句子是否包含颜文字
                boolean containsKaomoji = false;
                if (currentSentence.length() >= 3) { // 颜文字至少需要3个字符
                    containsKaomoji = EmojiUtils.containsKaomoji(currentSentence.toString());
                }

                // 如果当前字符是句号，检查它是否是数字中的小数点
                if (isEndMark && charStr.equals(".")) {
                    String context = contextBuffer.toString();
                    Matcher numberMatcher = NUMBER_PATTERN.matcher(context);
                    // 如果找到数字模式（如"0.271"），则不视为句子结束标点
                    if (numberMatcher.find() && numberMatcher.end() >= context.length() - 3) {
                        isEndMark = false;
                    }
                }

                // 判断是否应该发送当前句子
                if (isEndMark) {
                    // 句子结束标点是强断句信号
                    shouldSendSentence = true;
                } else if (isNewline) {
                    // 换行符也是强断句信号
                    shouldSendSentence = true;
                } else if ((isPauseMark || isSpecialMark || isEmoji || containsKaomoji)
                    && currentSentence.length() >= MIN_SENTENCE_LENGTH) {
                    // 停顿标点、特殊标点、表情符号或颜文字在句子足够长时可以断句
                    shouldSendSentence = true;
                }

                // 如果应该发送句子，且当前句子长度满足要求
                if (shouldSendSentence && currentSentence.length() >= MIN_SENTENCE_LENGTH) {
                    String sentence = currentSentence.toString().trim();

                    // 过滤颜文字
                    sentence = EmojiUtils.filterKaomoji(sentence);

                    if (containsSubstantialContent(sentence)) {
                        boolean isFirst = sentenceCount.get() == 0;
                        boolean isLast = false; // 只有在onComplete中才会有最后一个句子

                        sentenceHandler.accept(sentence, isFirst, isLast);
                        sentenceCount.incrementAndGet();

                        // 清空当前句子缓冲区
                        currentSentence.setLength(0);
                    }
                }

                // 移动到下一个码点
                i += Character.charCount(codePoint);
            }
        }

        @Override
        public void onComplete() throws IOException {
            // 检查该会话是否已完成处理
            // 处理当前缓冲区剩余的内容（如果有）
            if (!currentSentence.isEmpty() && containsSubstantialContent(currentSentence.toString())
                && !finalSentenceSent.get()) {
                String sentence = currentSentence.toString().trim();
                boolean isFirst = sentenceCount.get() == 0;
                boolean isLast = true; // 这是最后一个句子

                sentenceHandler.accept(sentence, isFirst, isLast);
                sentenceCount.incrementAndGet();
                finalSentenceSent.set(true);
            } else if (!finalSentenceSent.get()) {
                // 如果没有剩余内容但也没有发送过最后一个句子，发送一个空的最后句子标记
                // 这确保即使没有剩余内容，也会发送最后一个句子标记
                boolean isFirst = sentenceCount.get() == 0;
                sentenceHandler.accept("", isFirst, true);
                finalSentenceSent.set(true);
            }

            persistMessages();

            // 记录处理的句子数量
            log.debug("总共处理了 {} 个句子", sentenceCount.get());
        }

        /**
         * 保存消息,只保存用户输入与输出。
         * Message在没有持久化前，是不会有messageId的。
         */
        void persistMessages() {
            // TODO 是否需要把content为空和角色为tool的入库? 目前不入库（这类主要是function_call的二次调用llm进行总结时的过程消息）
            // 如果本轮对话是function_call或mcp调用(最后一条信息的类型)，把用户的消息类型也修正为同样类型
            // String lastMessageType = allMessages.get(allMessages.size() - 1).get("messageType").toString();
            // TODO 需要进一步看看ChatModel在流式响应里是如何判断hasTools的，或者直接基于Flux<ChatResponse>已封装好的对象hasToolCalls判断
            UserMessage userMessage = new UserMessage(message);

            Thread.startVirtualThread(() -> {//异步持久化
                addMessage(userMessage.getText(), userMessage.getMessageType().getValue(), "NORMAL", null, LoginHelper.getLoginUser());
                if (!fullResponse.isEmpty()) {
                    AssistantMessage assistantMessage = new AssistantMessage(fullResponse.toString());
                    addMessage(assistantMessage.getText(), assistantMessage.getMessageType().getValue(), "NORMAL", null, LoginHelper.getLoginUser());
                }
            });
        }

        @Override
        public void onError(Throwable e) {
            log.error("流式响应出错: {}", e.getMessage(), e);
            // 发送错误信号
            try {
                sentenceHandler.accept("抱歉，我在处理您的请求时遇到了问题。", true, true);
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }

        }
    }
}
